import { noop } from '../../constants';
import { FabricObject } from 'fabric';
import { createImage } from './dom';
import { classRegistry, Pattern } from 'fabric';



export const loadImage = ( url, { signal, crossOrigin = null } = {}) =>
  new Promise(function (resolve, reject) {
    if (signal && signal.aborted) {
      return reject(new Error('`options.signal` is in `aborted` state'));
    }
    const img = createImage();
    let abort;
    if (signal) {
      abort = function (err) {
        img.src = '';
        reject(err);
      };
      signal.addEventListener('abort', abort, { once: true });
    }
    const done = function () {
      img.onload = img.onerror = null;
      abort && signal?.removeEventListener('abort', abort);
      resolve(img);
    };
    if (!url) {
      done();
      return;
    }
    img.onload = done;
    img.onerror = function () {
      abort && signal?.removeEventListener('abort', abort);
      reject(new Error('Error loading ' + img.src));
    };
    crossOrigin && (img.crossOrigin = crossOrigin);
    img.src = url;
  });

export const enlivenObjects = ( objects, { signal, reviver = noop } = {} ) =>
  new Promise((resolve, reject) => {
    const instances= [];
    signal && signal.addEventListener('abort', reject, { once: true });
    Promise.all(
      objects.map((obj) =>
        (classRegistry
          .getClass(obj.type))
          .fromObject(obj, { signal, reviver})
          .then((fabricInstance: T) => {
            reviver<T>(obj, fabricInstance);
            instances.push(fabricInstance);
            return fabricInstance;
          })
      )
    )
      .then(resolve)
      .catch((error) => {
        // cleanup
        instances.forEach((instance) => {
          (instance ).dispose &&
            (instance).dispose();
        });
        reject(error);
      })
      .finally(() => {
        signal && signal.removeEventListener('abort', reject);
      });
  });


export const enlivenObjectEnlivables = ( serializedObject, { signal } = {}) =>
  new Promise((resolve, reject) => {
    const instances = [];
    signal && signal.addEventListener('abort', reject, { once: true });
    // enlive every possible property
    const promises = Object.values(serializedObject).map((value) => {
      if (!value) {
        return value;
      }
      // gradient
      if (value.colorStops) {
        return new (classRegistry.getClass('gradient'))(value);
      }
      // clipPath
      if (value.type) {
        return enlivenObjects([value], { signal }).then(
          ([enlived]) => {
            instances.push(enlived);
            return enlived;
          }
        );
      }
      // pattern
      if (value.source) {
        return (classRegistry
          .getClass('pattern'))
          .fromObject(value, { signal })
          .then((pattern) => {
            instances.push(pattern);
            return pattern;
          });
      }
      return value;
    });
    const keys = Object.keys(serializedObject);
    Promise.all(promises)
      .then((enlived) => {
        return enlived.reduce((acc, instance, index) => {
          acc[keys[index]] = instance;
          return acc;
        }, {});
      })
      .then(resolve)
      .catch((error) => {
        // cleanup
        instances.forEach((instance) => {
          instance.dispose && instance.dispose();
        });
        reject(error);
      })
      .finally(() => {
        signal && signal.removeEventListener('abort', reject);
      });
  });
